using System;
using System.IO;
using System.Globalization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Security.Permissions;
#if !PocketPC || DesignTime
using System.Runtime.Serialization;
using System.ComponentModel;
#endif
using System.Reflection;

namespace GeoFramework
{

	/// <summary>
	/// Represents a unit of angular measurement used during trigonometric
	/// equations.
	/// </summary>
	/// <remarks>
	/// 	<para>A radian is a unit of measure of an angle formed by an arc whose length is
	///     the same as the circle's radius, making a shape similar to a slice of pizza.
	///     Radians are typically used during trigonometric calculations such as calculating
	///     the distance between two points on Earth's curved surface.</para>
	/// 	<para>Instances of this class are guaranteed to be thread-safe because the class is
	///     immutable (its properties can only be changed during constructors).</para>
	/// </remarks>
#if !PocketPC || DesignTime
#if Framework20
    [Obfuscation(Feature = "renaming", Exclude = false, ApplyToMembers = true)]
    [Obfuscation(Feature = "controlflow", Exclude = true, ApplyToMembers = true)]
    [Obfuscation(Feature = "stringencryption", Exclude = false, ApplyToMembers = true)]
#endif
	[TypeConverter(typeof(ExpandableObjectConverter))]
	[Serializable()]
    [ImmutableObject(true)]
#endif
#if Framework20
    [XmlSchemaProvider("GetXmlSchema")]
#endif
    public struct Radian : IComparable, IFormattable
#if !PocketPC || (PocketPC && Framework20)
	, IXmlSerializable
#endif  
#if !PocketPC || DesignTime
, ISerializable
#endif
    {

        #region Constants

        public const double RadiansPerDegree = Math.PI / 180.0; 
        public const double DegreesPerRadian = 180.0 / Math.PI; 

        ////private const int DefaultPrecisionDigits = 10;

        #endregion

        #region Private variables

        private double _Value;

        #endregion

        #region Static members

        /// <summary>Represents a radian with a value of zero.</summary>
		public static readonly Radian Empty = new Radian(0.0);

        #endregion


		#region Alternative Operators
		/// <summary>Adds the current instance to the specified value.</summary>
		public Radian Add(Radian value)
		{
			return Add(value.Value);
		}

        public Radian Add(double value)
        {
            return new Radian(_Value + value);
        }

        public Radian Subtract(Radian value)
        {
            return Subtract(value.Value);
        }

        public Radian Subtract(double value)
		{
			return new Radian(_Value - value);
		}

		public Radian Multiply(Radian value)
        {
            return Multiply(value.Value);
        }

        public Radian Multiply(double value)
		{
			return new Radian(_Value * value);
		}

		/// <summary>Returns the current value divided by the specified value.</summary>
		public Radian Divide(Radian value)
		{
			return new Radian(Value / value.Value);
		}

        public Radian Divide(double value)
        {
            return new Radian(_Value / value);
        }

		public Radian Increment()
		{
			return new Radian(_Value + 1.0);
		}

		/// <summary>Returns the current value decreased by one.</summary>
		public Radian Decrement()
		{
			return new Radian(_Value - 1.0);
		}

		public bool IsLessThan(Radian value)
		{
			return CompareTo(value) < 0;
		}

        public bool IsLessThan(double value)
        {
            return CompareTo(value) < 0;
        }

		public bool IsLessThanOrEqualTo(Radian value)
		{
			return CompareTo(value) < 0 || Equals(value);
		}

        public bool IsLessThanOrEqualTo(double value)
        {
            return CompareTo(value) < 0 || Equals(value);
        }

		public bool IsGreaterThan(Radian value)
		{
			return CompareTo(value) > 0;
		}

        public bool IsGreaterThan(double value)
        {
            return CompareTo(value) > 0;
        }

		public bool IsGreaterThanOrEqualTo(Radian value)
		{
			return CompareTo(value) > 0 || Equals(value);
		}

        public bool IsGreaterThanOrEqualTo(double value)
        {
            return CompareTo(value) > 0 || Equals(value);
        }
		#endregion

		#region Overloaded Operators
		public static Radian operator +(Radian left, Radian right) 
		{
			return left.Add(right);
		}	

		public static Radian operator -(Radian left, Radian right) 
		{
			return left.Subtract(right);
		}

		public static Radian operator *(Radian left, Radian right) 
		{
			return left.Multiply(right);
		}

		public static Radian operator /(Radian left, Radian right) 
		{
			return left.Divide(right);
		}

		public static bool operator <(Radian left, Radian right) 
		{
			return left.CompareTo(right) < 0;
		}

		public static bool operator <=(Radian left, Radian right) 
		{
			return left.CompareTo(right) < 0 || left.Equals(right);
		}

		public static bool operator ==(Radian left, Radian right) 
		{
			return left.Equals(right);
		}

		public static bool operator !=(Radian left, Radian right) 
		{
			return !(left == right);
		}

		public static bool operator >=(Radian left, Radian right) 
		{
			return left.CompareTo(right) > 0 || left.Equals(right);
		}
	
		public static bool operator >(Radian left, Radian right) 
		{
			return left.CompareTo(right) > 0;
		}

        public static Radian operator +(Radian left, double right)
        {
            return left.Add(right);
        }

        public static Radian operator -(Radian left, double right)
        {
            return left.Subtract(right);
        }

        public static Radian operator *(Radian left, double right)
        {
            return left.Multiply(right);
        }

        public static Radian operator /(Radian left, double right)
        {
            return left.Divide(right);
        }

        public static bool operator <(Radian left, double right)
        {
            return left.CompareTo(right) < 0;
        }

        public static bool operator <=(Radian left, double right)
        {
            return left.CompareTo(right) < 0 || left.Equals(right);
        }

        public static bool operator ==(Radian left, double right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Radian left, double right)
        {
            return !(left == right);
        }

        public static bool operator >=(Radian left, double right)
        {
            return left.CompareTo(right) > 0 || left.Equals(right);
        }

        public static bool operator >(Radian left, double right)
        {
            return left.CompareTo(right) > 0;
        }

		#endregion

		#region Conversions
		
        public static implicit operator double(Radian value)
		{
			return value.Value;
		}

		public static explicit operator Radian(Longitude value) 
		{
            return value.ToRadians();
		}

        public static explicit operator Radian(Latitude value) 
		{
            return value.ToRadians();
		}
		
        public static explicit operator Radian(Azimuth value) 
		{
            return value.ToRadians();
		}

		public static explicit operator Radian(Angle value) 
		{
            return value.ToRadians();
		}

		public static explicit operator Radian(double value) 
		{
			return new Radian(value);
		}

		public static explicit operator Radian(string value) 
		{
			return new Radian(value, CultureInfo.CurrentCulture);
		}
		#endregion

		/// <summary>Creates a new instance with the specified value.</summary>
		/// <remarks>
		/// this constructor is typically used to initialize an instance when the radian
		/// value is already known.
		/// </remarks>
		/// <param name="value">A value to store in the <strong>Value</strong> property.</param>
		public Radian(double value)
		{
			_Value = value;
		}

		public Radian(int value)
		{
			_Value = (double)value;
		}

		public Radian(string value)
			: this(value, CultureInfo.CurrentCulture)
		{
		}

		public Radian(string value, CultureInfo culture)
		{
			_Value = double.Parse(value, culture);
		}

#if !PocketPC || DesignTime
        /// <summary>
        /// Creates a new instance by de-serializing previously-serialized information.
        /// </summary>
        /// <param name="info">A <strong>SerializationInfo</strong> object containing serialized data.</param>
        /// <param name="context">A <strong>StreamingContext</strong> object describing the context of serialization.</param>
        /// <remarks>This constructor will de-serialize a previously-serialized object.</remarks>
        private Radian(SerializationInfo info, StreamingContext context)
        {
            _Value = info.GetDouble("Value");
        }
#endif


		public static Radian Parse(string value)
		{
			return new Radian(value, CultureInfo.CurrentCulture);
		}

		public static Radian Parse(string value, CultureInfo culture)
		{
			return new Radian(value, culture);
		}

		public bool Equals(Radian value)
		{
			return Equals(value.Value);
		}

		public bool Equals(Radian value, int decimals)
        {
            return Equals(value.Value, decimals);
		}

        public bool Equals(double value)
        {
            return _Value.Equals(value);
        }

        public bool Equals(double value, int decimals)
        {
            return Math.Round(_Value, decimals) == Math.Round(value, decimals);
        }

        public bool Equals(string value)
        {
            return Equals(value, CultureInfo.CurrentCulture);
        }

        public bool Equals(string value, CultureInfo culture)
        {
            return _Value.Equals(new Radian(value, culture));
        }

		public override bool Equals(object obj)
		{
			// Now compare based on the property type
            if (obj is Radian)
                return Equals((Radian)obj);
            else if (obj is string)
                return Equals(new Radian(Convert.ToString(obj), CultureInfo.CurrentCulture));
            else if (obj is double)
                return Equals(new Radian(Convert.ToDouble(obj)));
            else
                return base.Equals(obj);
		}

		/// <summary>Returns the cosine of the current instance.</summary>
		public Radian Cosine()
		{
			return new Radian(Math.Cos(_Value));
		}

		public Radian Sine()
		{
			return new Radian(Math.Sin(_Value));
		}

		public Radian Tangent()
		{
			return new Radian(Math.Tan(_Value));
		}

		public Radian SquareRoot()
		{
			return new Radian(Math.Sqrt(_Value));
		}

		/// <summary>Returns the absolute value of the current instance.</summary>
		public Radian AbsoluteValue()
		{
			return new Radian(Math.Abs(_Value));
		}

		/// <summary>Returns the arccosine of the current instance.</summary>
		public Radian ArcCosine()
		{
			return new Radian(Math.Acos(_Value));
		}
	
		/// <summary>Returns the arcsine of the current instance.</summary>
		public Radian ArcSine()
		{
			return new Radian(Math.Asin(_Value));
		}

		/// <summary>Returns the arctangent of the current instance.</summary>
		public Radian ArcTangent()
		{
			return new Radian(Math.Atan(_Value));
		}

#if !PocketPC
		public Radian Logarithm(double newBase)
		{
			return new Radian(Math.Log(_Value, newBase));
		}
#endif

		public Radian LogarithmBase10()
		{
			return new Radian(Math.Log10(_Value));
		}

		/// <summary>Returns the unique code for this instance used in hash tables.</summary>
		public override int GetHashCode()
		{
			return _Value.GetHashCode();
		}

		// Returns/sets the number of radians
		/// <summary>Represents the numeric portion of a radian measurement.</summary>
		/// <value>
		/// A <strong>Double</strong> value indicating an angular measurement expressed in
		/// radians.
		/// </value>
		/// <remarks>
		/// This property stores the numeric radian measurement. A radian can be converted into a degree
		/// measurements via the <see cref="Radian.ToAngle(Radian)">ToAngle</see> method.
		/// </remarks>
		public double Value
		{
			get
			{
				return _Value;
			}
		}

        ///// <summary>
        ///// Converts the specified angle into radians, storing the result in the current
        ///// instance.
        ///// </summary>
        ///// <remarks>
        ///// This method is typically used when a Radian object is being reused for
        ///// conversions from multiple
        ///// <see cref=" 
        /////Angle">Angle</see>
        ///// objects.
        ///// </remarks>
        ///// <param name="value">An <strong>Angle</strong> containing the value to convert.</param>
        //public static Radian FromAngle(Angle value)
        //{
        //    return new Radian(value.DecimalDegrees * RadiansPerDegree);
        //}

        ///// <summary>
        ///// Converts the specified angle into radians, storing the result in the current
        ///// instance.
        ///// </summary>
        ///// <remarks>
        ///// This method is typically used when a Radian object is being reused for
        ///// conversions from multiple
        ///// <see cref=" 
        /////Azimuth">Azimuth</see>
        ///// objects.
        ///// </remarks>
        ///// <param name="value">An <strong>Azimuth</strong> containing the value to convert.</param>
        //public static Radian FromAzimuth(Azimuth value)
        //{
        //    return new Radian(value.DecimalDegrees * RadiansPerDegree);
        //}

        ///// <summary>
        ///// Converts the specified angle into radians, storing the result in the current
        ///// instance.
        ///// </summary>
        ///// <remarks>
        ///// This method is typically used when a Radian object is being reused for
        ///// conversions from multiple
        ///// <see cref=" 
        /////Elevation">Elevation</see>
        ///// objects.
        ///// </remarks>
        ///// <param name="value">An <strong>Elevation</strong> containing the value to convert.</param>
        //public static Radian FromElevation(Elevation value)
        //{
        //    return new Radian(value.DecimalDegrees * RadiansPerDegree);
        //}

        ///// <summary>
        ///// Converts the specified angle into radians, storing the result in the current
        ///// instance.
        ///// </summary>
        ///// <remarks>
        ///// This method is typically used when a Radian object is being reused for
        ///// conversions from multiple
        ///// <see cref=" 
        /////Latitude">Latitude</see>
        ///// objects.
        ///// </remarks>
        ///// <param name="value">An <strong>Latitude</strong> containing the value to convert.</param>
        //public static Radian FromLatitude(Latitude value)
        //{
        //    return new Radian(value.DecimalDegrees * RadiansPerDegree);
        //}

        ///// <summary>
        ///// Converts the specified angle into radians, storing the result in the current
        ///// instance.
        ///// </summary>
        ///// <remarks>
        ///// This method is typically used when a Radian object is being reused for
        ///// conversions from multiple
        ///// <see cref=" 
        /////Longitude">Longitude</see>
        ///// objects.
        ///// </remarks>
        ///// <param name="value">An <strong>Longitude</strong> containing the value to convert.</param>
        //public static Radian FromLongitude(Longitude value)
        //{
        //    return new Radian(value.DecimalDegrees * RadiansPerDegree);
        //}

        /// <summary>
        /// Converts the specified value in degrees into radians.
        /// </summary>
        /// <param name="value">A <strong>Double</strong> containing the value to convert.</param>
        public static Radian FromDegrees(double value)
        {
            return new Radian(value * RadiansPerDegree);
        }

        /// <summary>
        /// Converts the specified value in degrees into radians.
        /// </summary>
        /// <param name="value">An <strong>Angle</strong> containing the value to convert.</param>
        public static Radian FromAngle(Angle value)
        {
            return new Radian(value.DecimalDegrees * RadiansPerDegree);
        }

        /// <summary>Converts the current instance into an <strong>Angle</strong> object.</summary>
        /// <returns>An <strong>Angle</strong> object.</returns>
        /// <remarks>
        /// This method is typically used to convert a radian measurement back to latitude or
        /// longitude after a trigonometric formula has completed.
        /// </remarks>
        public double ToDegrees()
        {
            return _Value / RadiansPerDegree;
        }

        /// <summary>Converts the specified value from radians to degrees.</summary>
        /// <returns>A <strong>Double</strong> measuring degrees.</returns>
        /// <remarks>
        /// This method is typically used to convert a radian measurement back to latitude or
        /// longitude after a trigonometric formula has completed.
        /// </remarks>
        public static double ToDegrees(double radians)
        {
            return radians / RadiansPerDegree;
        }

        /// <summary>Converts the current instance into an <strong>Angle</strong> object.</summary>
        /// <returns>An <strong>Angle</strong> object.</returns>
        /// <remarks>
        /// This method is typically used to convert a radian measurement back to latitude or
        /// longitude after a trigonometric formula has completed.
        /// </remarks>
        public Angle ToAngle()
        {
            return new Angle(ToDegrees());
        }

		/// <summary>
		/// Converts a Radian object into decimal degrees.
		/// </summary>
		/// <param name="value">A <strong>Radian</strong> object to convert to an <strong>Angle</strong>.</param>
		/// <returns>An <strong>Angle</strong> object containing the converted value.</returns>
		/// <remarks>This method is typically used for trigonometric functions which work with values expressed as radians.  Then the formula has completed, results are converted from radians to decimal degrees to make them easier to use.</remarks>
		public static Angle ToAngle(Radian value)
		{
            return value.ToAngle();
		}

        ///// <summary>Converts the current instance into an <strong>Azimuth</strong> object.</summary>
        ///// <returns>An <strong>Azimuth</strong> object.</returns>
        ///// <remarks>
        ///// This method is typically used to convert a radian measurement back to latitude or
        ///// longitude after a trigonometric formula has completed.
        ///// </remarks>
        //public Azimuth ToAzimuth()
        //{
        //    return new Azimuth(_Value / RadiansPerDegree);
        //}

        ///// <summary>
        ///// Converts a Radian object into decimal degrees.
        ///// </summary>
        ///// <param name="value">A <strong>Radian</strong> object to convert to an <strong>Azimuth</strong>.</param>
        ///// <returns>An <strong>Azimuth</strong> object containing the converted value.</returns>
        ///// <remarks>This method is typically used for trigonometric functions which work with values expressed as radians.  Then the formula has completed, results are converted from radians to decimal degrees to make them easier to use.</remarks>
        //public static Azimuth ToAzimuth(Radian value)
        //{
        //    return value.ToAzimuth();
        //}

        ///// <summary>Converts the current instance into an <strong>Latitude</strong> object.</summary>
        ///// <returns>An <strong>Latitude</strong> object.</returns>
        ///// <remarks>
        ///// This method is typically used to convert a radian measurement back to latitude or
        ///// longitude after a trigonometric formula has completed.
        ///// </remarks>
        //public Latitude ToLatitude()
        //{
        //    return new Latitude(_Value / RadiansPerDegree);
        //}

        ///// <summary>
        ///// Converts a Radian object into decimal degrees.
        ///// </summary>
        ///// <param name="value">A <strong>Radian</strong> object to convert to an <strong>Latitude</strong>.</param>
        ///// <returns>An <strong>Latitude</strong> object containing the converted value.</returns>
        ///// <remarks>This method is typically used for trigonometric functions which work with values expressed as radians.  Then the formula has completed, results are converted from radians to decimal degrees to make them easier to use.</remarks>
        //public static Latitude ToLatitude(Radian value)
        //{
        //    return value.ToLatitude();
        //}

        ///// <summary>Converts the current instance into an <strong>Longitude</strong> object.</summary>
        ///// <returns>An <strong>Longitude</strong> object.</returns>
        ///// <remarks>
        ///// This method is typically used to convert a radian measurement back to latitude or
        ///// longitude after a trigonometric formula has completed.
        ///// </remarks>
        //public Longitude ToLongitude()
        //{
        //    return new Longitude(_Value / RadiansPerDegree);
        //}

        ///// <summary>
        ///// Converts a Radian object into decimal degrees.
        ///// </summary>
        ///// <param name="value">A <strong>Radian</strong> object to convert to an <strong>Longitude</strong>.</param>
        ///// <returns>An <strong>Longitude</strong> object containing the converted value.</returns>
        ///// <remarks>This method is typically used for trigonometric functions which work with values expressed as radians.  Then the formula has completed, results are converted from radians to decimal degrees to make them easier to use.</remarks>
        //public static Longitude ToLongitude(Radian value)
        //{
        //    return value.ToLongitude();
        //}

        ///// <summary>Converts the current instance into an <strong>Elevation</strong> object.</summary>
        ///// <returns>An <strong>Elevation</strong> object.</returns>
        ///// <remarks>
        ///// This method is typically used to convert a radian measurement back to latitude or
        ///// longitude after a trigonometric formula has completed.
        ///// </remarks>
        //public Elevation ToElevation()
        //{
        //    return new Elevation(_Value / RadiansPerDegree);
        //}

        ///// <summary>
        ///// Converts a Radian object into decimal degrees.
        ///// </summary>
        ///// <param name="value">A <strong>Radian</strong> object to convert to an <strong>Elevation</strong>.</param>
        ///// <returns>An <strong>Elevation</strong> object containing the converted value.</returns>
        ///// <remarks>This method is typically used for trigonometric functions which work with values expressed as radians.  Then the formula has completed, results are converted from radians to decimal degrees to make them easier to use.</remarks>
        //public static Elevation ToElevation(Radian value)
        //{
        //    return value.ToElevation();
        //}


		public override string ToString()
		{
			return ToString("g", CultureInfo.CurrentCulture); // Always support "g" as a default format
		}

		/// <summary>
		/// Outputs the speed measurement as a formatted string using the specified
		/// format.
		/// </summary>
		public string ToString(string format)
		{
			return ToString(format, CultureInfo.CurrentCulture);
		}

		/// <summary>
		/// Outputs the speed measurement as a formatted string using the specified format
		/// and culture information.
		/// </summary>
		public string ToString(string format, System.IFormatProvider formatProvider)
		{
            return Value.ToString(format, formatProvider);
        }

		/// <summary>Compares the current instance with the specified value.</summary>
		public int CompareTo(Radian value)
		{
            return CompareTo(value.Value);
        }

        /// <summary>Compares the current instance with the specified value.</summary>
        public int CompareTo(double value)
        {
            return _Value.CompareTo(value);
        }

        #region IComparable Members

        int IComparable.CompareTo(object obj)
        {
            return this.CompareTo((Radian)obj);            
        }

        #endregion

        #region IFormattable Members

        string IFormattable.ToString(string format, IFormatProvider formatProvider)
        {
            return this.ToString(format, formatProvider);
        }

        #endregion

#if !PocketPC || (PocketPC && Framework20)
		#region IXmlSerializable Members

#if Framework20
        public static XmlQualifiedName GetXmlSchema(XmlSchemaSet xs)
        {
            // Load it from a string
            xs.Add(Xml.SchemaSet);

            // And return the type
            return new XmlQualifiedName("Radian", Xml.Namespace);
        }
#endif

        XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }


        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            ReadXml(reader);
        }

        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            WriteXml(writer);
        }

        /// <summary>
        /// Populates the current instance by reading XML from the specified reader.
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            reader.ReadStartElement();
            _Value = double.Parse(reader.ReadString(), CultureInfo.InvariantCulture);
            reader.ReadEndElement();
        }

        /// <summary>
        /// Writes the values of the current instance to the specified XML writer.
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(XmlWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteString(_Value.ToString("G15", CultureInfo.InvariantCulture));
        }

		#endregion
#endif

#if !PocketPC || DesignTime
        #region ISerializable Members

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]  
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Value", _Value);
        }
        #endregion
#endif
    }

    public sealed class RadianEventArgs : EventArgs
	{
		private Radian oRadians;

		public static readonly new RadianEventArgs Empty = new RadianEventArgs(Radian.Empty);

		public RadianEventArgs(Radian radian)
		{
			oRadians = radian;
		}

		public Radian Radians
		{
			get
			{
				return oRadians;
			}
		}
	}

}